﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PServiceBus.Core.Interface;
using System.Threading;
using PServiceBus.Core.Runtime.Extensions;
using PServiceBus.Core.Runtime.Transports;
using System.Reflection;
using System.Collections;

namespace PServiceBus.Core.Runtime.Messages {
    public class MessageHandlerBase<TMessage, TFormatType> : IMessageHandler<TMessage> where TMessage : class where TFormatType : class {

        private volatile bool _running;
        private RegisteredWaitHandle _handle;
        private ManualResetEvent _reset = new ManualResetEvent(false);
        private bool _isMessageObjectType = typeof(TMessage) == typeof(object);
        private readonly static MethodInfo _transportProviderMessageTo = typeof(TransportProvider).GetMethod("MessageTo",
            BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);

        public IObjectProvider<TFormatType> Provider { get; set; }

        public static MessageHandlerBase<TMessage, TFormatType> New<TTransport>(ITransportHandler<TTransport> transportHandler, 
            IObjectProvider<TFormatType> provider, Func<string> endpointFunc) where TTransport : ITransport {
            provider.DeleteObjectOnRead = true;
            provider.IDFunc = o => Guid.NewGuid();
            provider.Endpoint = endpointFunc();
            return new MessageHandlerBase<TMessage, TFormatType>() {
                BatchSize = transportHandler.BatchSize,
                Interval = transportHandler.Interval,
                Provider = provider
            };
        }

        #region IMessageHandler<TMessage> Members

        public string Endpoint { set { if (Provider != null) Provider.Endpoint = value; } }

        public virtual void Init() { }

        public int BatchSize { get; set; }

        public TimeSpan Interval { get; set; }

        public event EventHandler<MessageReceivedArgs<TMessage>> MessageReceived;

        public event EventHandler<HandleErrorArgs<TMessage>> ErrorReceived;

        
        public void OnMessageReceived(MessageReceivedArgs<TMessage> e) {
            if (MessageReceived != null) MessageReceived(this, e);
        }

        public void OnErrorReceived(HandleErrorArgs<TMessage> e) {
            if (ErrorReceived != null) ErrorReceived(this, e);
        }

        public bool IsRunning {
            get { return _running; }
        }

        public bool HandleInListenerThread { get; set; }

        public void Start() {
            _running = true;
            this.HandleError(MethodHelper.Try(Init), null);
            _reset.Reset();
            _handle = ThreadPool.RegisterWaitForSingleObject(_reset, new WaitOrTimerCallback(ProcessEvent),
                null, (int)Interval.TotalMilliseconds, true);
        }

        public void Stop() {
            if (!_running) return;
            _running = false;
            _reset.Set();
            ClearHandlers();
            Shutdown();
        }

        protected virtual void Shutdown() {
            if (Provider != null) Provider.Dispose();
        }

        
        protected virtual IEnumerable<TMessage> GetMessages(TFormatType data) {
            var message = data as string;
            return _isMessageObjectType ? TransportProvider.MessageObjectTo<TMessage>(message) :
                TransportProvider.MessageTo<TMessage>(message);
        }

        private void ProcessEvent(object state, bool timeout) {
            if (timeout) {
                var counter = 0;
                var rawMessage = string.Empty;
                try {
                    foreach (var data in Provider) {
                        if (data == null) continue;
                        rawMessage = data as string;
                        var messages = GetMessages(data);
                        this.Handle(messages.ToList(), rawMessage);
                        if (++counter == BatchSize) break;
                    }
                } catch (Exception ex) {
                    this.HandleError(ex, null, rawMessage: rawMessage);
                }
                _handle = ThreadPool.RegisterWaitForSingleObject(_reset, new WaitOrTimerCallback(ProcessEvent),
                    null, (int)Interval.TotalMilliseconds, true);
            } else {
                _handle.Unregister(null);
            }
        }

        private List<EventHandler<MessageReceivedArgs<TMessage>>> _messageHandlers = new List<EventHandler<MessageReceivedArgs<TMessage>>>();
        private List<EventHandler<HandleErrorArgs<TMessage>>> _errorHandlers = new List<EventHandler<HandleErrorArgs<TMessage>>>();

        private void ClearHandlers() {
            for (var i = 0; i < _messageHandlers.Count; i++)
                MessageReceived -= _messageHandlers[i];
            for (var i = 0; i < _errorHandlers.Count; i++)
                ErrorReceived -= _errorHandlers[i];
            _messageHandlers.Clear();
            _errorHandlers.Clear();
            MessageReceived = null;
            ErrorReceived = null;
        }

        event EventHandler<MessageReceivedArgs<TMessage>> IMessageHandler<TMessage>.MessageReceived {
            add { MessageReceived += value; _messageHandlers.Add(value); }
            remove { MessageReceived -= value; }
        }

        event EventHandler<HandleErrorArgs<TMessage>> IMessageHandler<TMessage>.ErrorReceived {
            add { ErrorReceived += value; _errorHandlers.Add(value); }
            remove { ErrorReceived -= value; }
        }

        #endregion
    }
}
